#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <mutex>

#include <unistd.h>
#include <uv.h>

#define _UVERR(call, err) std::cerr << call << " failed because " << uv_strerror(err) << std::endl
#define UVERR(call, err) { if (err) { _UVERR(call, err); } }
#define UVERRRET(call, err) { if (err) { _UVERR(call, err); return; } }
#define UVERRRETVAL(call, err) { if (err) { _UVERR(call, err); return err; } }
#define UVCALL(call, ...) { if (auto err = call(__VA_ARGS__)) { UVERR(#call, err); } }
#define UVCALLRET(call, ...) { if (auto err = call(__VA_ARGS__)) { UVERRRET(#call, err); } }
#define UVCALLRETVAL(call, ...) { if (auto err = call(__VA_ARGS__)) { UVERRRETVAL(#call, err); } }

class EpollIOWaiter {
public:
    ~EpollIOWaiter()
    {
        uv_signal_stop(&signal_);
        uv_timer_stop(&timer_);
        uv_stop(&loop_);
        uv_loop_close(&loop_);
    }

    int Init()
    {
        UVCALLRETVAL(uv_loop_init, &loop_);
        UVCALLRETVAL(uv_signal_init, &loop_, &signal_);
        UVCALLRETVAL(uv_signal_start, &signal_, OnSignal, SIGINT);
        UVCALLRETVAL(uv_timer_init, &loop_, &timer_);
        return 0;
    }

    bool WaitFor(std::unique_lock<std::mutex> &lock, int64_t ns)
    {
        UVCALL(uv_timer_start, &timer_, OnTimer, ns / 1e6, 0);
        lock.unlock();
        uv_run(&loop_, UV_RUN_DEFAULT);
        lock.lock();
        return overtime;
    }

    void NotifyOne()
    {
        NotifyAll();
    }

    void NotifyAll()
    {
        std::lock_guard lock(timerMutex_);
        UVCALL(uv_timer_start, &timer_, OnTimer, 0, 0);
    }

    enum class Event : uint8_t {
        READABLE = UV_READABLE,
        WRITABLE = UV_WRITABLE,
        RW = UV_READABLE | UV_WRITABLE,
    };

    int AddFileDescriptor(int32_t fd, Event events)
    {
        if (pollMap_.find(fd) != pollMap_.end()) {
            return -1;
        }

        pollMap_[fd] = {.data = this};
        UVCALLRETVAL(uv_poll_init, &loop_, &pollMap_[fd], fd);
        UVCALLRETVAL(uv_poll_start, &pollMap_[fd], static_cast<uint8_t>(events), OnPoll);
        return 0;
    }

    int RemoveFileDescriptor(int32_t fd)
    {
        const auto &it = pollMap_.find(fd);
        if (it == pollMap_.end()) {
            return 1;
        }

        UVCALLRETVAL(uv_poll_stop, &it->second);
        return 0;
    }

    using FileDescriptorEventCallback = std::function<void(int32_t, uint32_t)>;
    void SetFileDescriptorEventCallback(const FileDescriptorEventCallback &callback)
    {
        fdcallback_ = callback;
    }

private:
    static void OnPoll(uv_poll_t *poll, int status, int events)
    {
        std::cout << __func__ << std::endl;
        UVERR(OnPoll, status);
        auto &that = GetInstance(poll);
        std::lock_guard lock(that.timerMutex_);
        UVCALL(uv_timer_stop, &that.timer_);
        uv_stop(&that.loop_);
        if (that.fdcallback_) {
            that.fdcallback_(poll->io_watcher.fd, events);
        }
        that.overtime = false;
    }

    static void OnTimer(uv_timer_t *timer)
    {
        std::cout << __func__ << std::endl;
        auto &that = GetInstance(timer);
        std::lock_guard lock(that.timerMutex_);
        UVCALL(uv_timer_stop, timer);
        uv_stop(&that.loop_);
        that.overtime = true;
    }

    static void OnSignal(uv_signal_t *signal, int32_t signum)
    {
        std::cout << __func__ << std::endl;
        auto &that = GetInstance(signal);
        if (signum == SIGINT) {
            UVCALL(uv_signal_stop, signal);
            that.NotifyAll();
        }
    }

    template<typename T>
    static EpollIOWaiter &GetInstance(T *that)
    {
        return *reinterpret_cast<EpollIOWaiter *>(that->data);
    }

    uv_loop_t loop_ = {.data = this};
    uv_signal_t signal_ = {.data = this};
    uv_timer_t timer_ = {.data = this};
    std::mutex timerMutex_;
    std::map<int32_t, uv_poll_t> pollMap_;
    bool overtime = false;
    FileDescriptorEventCallback fdcallback_ = nullptr;
};

int main()
{
    EpollIOWaiter waiter;
    waiter.Init();
    waiter.AddFileDescriptor(0, EpollIOWaiter::Event::READABLE);
    waiter.AddFileDescriptor(1, EpollIOWaiter::Event::WRITABLE);
    waiter.AddFileDescriptor(2, EpollIOWaiter::Event::WRITABLE);
    waiter.RemoveFileDescriptor(1);
    waiter.SetFileDescriptorEventCallback([](int32_t fd, uint32_t events) {
        std::cout << "fd: " << fd << ", events: " << events << std::endl;
        read(fd, &fd, sizeof(fd));
    });
    std::mutex mutex;
    std::unique_lock lock(mutex);
    waiter.WaitFor(lock, 1e10);
    waiter.RemoveFileDescriptor(2);
    waiter.WaitFor(lock, 1e10);
    return 0;
}
